#include "unixHeader.h"
#include "hash.h"
#include "token.h"
#include "oss.h"
#include "mq.h"
#include "user.pb.h"
#include "user.srpc.h"
#include <iostream>

#include <nlohmann/json.hpp>
#include <workflow/WFFacilities.h>
#include <workflow/MySQLResult.h>
#include <wfrest/HttpServer.h>

using namespace std;
using namespace wfrest;
using Json = nlohmann::json;

static WFFacilities::WaitGroup waitGroup(1);

void mysqlCallback(WFMySQLTask * )
{
}

void test0()
{
    HttpServer server;
    string mysqlurl("mysql://root:1234@localhost");

    server.GET("/file/upload", [](const HttpReq * , HttpResp * resp){
        resp->File("./static/view/index.html");
    });

    server.GET("/file/upload/success", [](const HttpReq * , HttpResp * resp){
        resp->String("upload success");
    });

    server.GET("/user/signup", [](const HttpReq * , HttpResp * resp){
        resp->File("./static/view/signup.html");
    });

    server.GET("/static/view/signin.html", [](const HttpReq * , HttpResp * resp){
        resp->File("./static/view/signin.html");
    });

    server.GET("/static/view/home.html", [](const HttpReq * , HttpResp * resp){
        resp->File("./static/view/home.html");
    });

    server.GET("/static/img/avatar.jpeg", [](const HttpReq * , HttpResp * resp){
        resp->File("./static/img/avatar.jpeg");
    });

    server.GET("/static/js/auth.js", [](const HttpReq * , HttpResp * resp){
        resp->File("./static/js/auth.js");
    });

    server.POST("/user/info", [mysqlurl](const HttpReq * req, HttpResp * resp, SeriesWork * series){
        //1. 解析请求
        auto queryList = req->query_list();
        string username = queryList["username"];
        string token = queryList["token"];
        //2. 校验token
        //3. 查询数据库MySQL
        auto mysqlTask = WFTaskFactory::create_mysql_task(mysqlurl,0,
            [resp, mysqlurl, username](WFMySQLTask * mysqltask){
                auto mysqlresp = mysqltask->get_resp();
                using namespace protocol;

                //进行读取操作
                MySQLResultCursor cursor(mysqlresp);
                vector<vector<MySQLCell>> rows;
                cursor.fetch_all(rows);
                if(rows[0][0].is_datetime()) {
                    Json msg;
                    Json data;
                    data["Username"] = username;
                    data["SignupAt"] = rows[0][0].as_datetime();
                    msg["data"] = data;
                    resp->String(msg.dump());
                } else {
                    resp->String("user info get failed");
                }
            });       
        string sql = "select signup_at from cloudisk.tbl_user where user_name = '" + username + "'";
        cout << "sql:\n" << sql << endl;
        mysqlTask->get_req()->set_query(sql);
        series->push_back(mysqlTask);
    });

    server.POST("/file/query", [mysqlurl](const HttpReq * req, HttpResp * resp, SeriesWork * series){
        //1. 解析请求
        auto queryList = req->query_list();
        string username = queryList["username"];
        string token = queryList["token"];
        auto formKV = req->form_kv();
        string limitcnt = formKV["limit"];
        cout << "limit:" << limitcnt << endl;

        //2. 校验token
        //3. 查询数据库MySQL
        auto mysqlTask = WFTaskFactory::create_mysql_task(mysqlurl,0,
            [resp, mysqlurl, username](WFMySQLTask * mysqltask){
                auto mysqlresp = mysqltask->get_resp();
                using namespace protocol;

                //进行读取操作
                MySQLResultCursor cursor(mysqlresp);
                vector<vector<MySQLCell>> rows;
                cursor.fetch_all(rows);
                if(rows.size() == 0) return;

                Json arrMsg;
                for(size_t i = 0; i < rows.size(); ++i) {
                    Json row;
                    row["FileHash"] = rows[i][0].as_string();
                    row["FileSize"] = rows[i][1].as_ulonglong();
                    row["FileName"] = rows[i][2].as_string();
                    row["UploadAt"] = rows[i][3].as_datetime();
                    row["LastUpdated"] = rows[i][4].as_datetime();
                    arrMsg.push_back(row);
                }
                resp->String(arrMsg.dump());
            });       
        string sql = "select file_sha1, file_size, file_name, upload_at,"
            " last_update from cloudisk.tbl_user_file where user_name = '" 
            + username + "' limit " + limitcnt;
        cout << "sql:\n" << sql << endl;
        mysqlTask->get_req()->set_query(sql);
        series->push_back(mysqlTask);
    });

    server.POST("/user/signin", [mysqlurl](const HttpReq * req, HttpResp * resp, SeriesWork * series){
        //1. 解析请求
        auto & formKV = req->form_kv();
        string username =formKV["username"];
        string password = formKV["password"];
        //2. 对用户的密码进行加密
        string salt("12345678");//优化: 从数据库中获取
        char * passwd  = crypt(password.c_str(), salt.c_str());
        string encodedpasswd(passwd);
        //3. 登录验证,是否为合法用户, 查询数据库
        auto mysqlTask = WFTaskFactory::create_mysql_task(mysqlurl,0,
            [resp, encodedpasswd, username, salt, mysqlurl](WFMySQLTask * mysqltask){
                auto mysqlresp = mysqltask->get_resp();
                int state = mysqltask->get_state();
                int error = mysqltask->get_error();
                if(state != WFT_STATE_SUCCESS) {
                    printf("error: %s\n", WFGlobal::get_error_string(state, error));
                    return;
                }

                //语法错误
                if(mysqlresp->get_packet_type() == MYSQL_PACKET_HEADER_ERROR) {
                    printf("error_code: %d, msg: %s\n",
                        mysqlresp->get_error_code(),
                        mysqlresp->get_error_msg().c_str());
                    resp->String("FAILED");
                    return;
                }

                using namespace protocol;
                //进行读取操作
                MySQLResultCursor cursor(mysqlresp);
                vector<vector<MySQLCell>> rows;
                cursor.fetch_all(rows);
                if(rows[0][0].is_string()) {
                    string result = rows[0][0].as_string();
                    //3.1 密码进行对比
                    if(encodedpasswd == result) { //登录成功
                        //3.2 生成Token
                        Token token(username, salt);
                        string strToken = token.getToken();
                        //3.3 访问数据库MySQL,写入用户的Token信息
                        auto mysqlTokenTask = WFTaskFactory::create_mysql_task(mysqlurl,0, mysqlCallback);
                        string sql = "REPLACE INTO cloudisk.tbl_user_token(`user_name`, `user_token`) VALUES('"
                            + username + "','" + strToken + "')";
                        cout << "sql:\n" << sql << endl;
                        mysqlTokenTask->get_req()->set_query(sql);
                        series_of(mysqltask)->push_back(mysqlTokenTask);
                        
                        //4. 生成JSON对象，返回给客户端
                        Json msg;
                        Json data;
                        data["Username"] = username;
                        data["Token"] = strToken;
                        data["Location"] = "/static/view/home.html";
                        msg["data"] = data;
                        resp->String(msg.dump());
                    } else {
                        //登录失败
                        resp->set_status_code("500");
                    }
                } else {
                    //登录失败
                    resp->set_status_code("500");
                }
        });
        string sql = "select user_pwd from cloudisk.tbl_user where user_name = '"
            + username + "' limit 1";
        cout << "sql:\n" << sql << endl;
        mysqlTask->get_req()->set_query(sql);
        series->push_back(mysqlTask);
    });

    server.POST("/user/signup", [mysqlurl](const HttpReq * req, 
        HttpResp * resp, SeriesWork * series) {
        //1. 解析请求
        auto & formKV = req->form_kv();
        string username =formKV["username"];
        string password = formKV["password"];
        //2. 待优化:从注册中心获取ip和port
	    const char *ip = "127.0.0.1";
	    unsigned short port = 1412;
        //3. 进行RPC请求
        GOOGLE_PROTOBUF_VERIFY_VERSION;
	    UserService::SRPCClient client(ip, port);
	    ReqSignup sigup_req;
        sigup_req.set_username(username);
        sigup_req.set_password(password);
        //3.1 因为rpc返回和回复响应给HTTP客户端是同步的，
        //    接下来必须使用serverTask的序列机制 
        //3.2 创建RPC任务
        auto rpcTask = client.create_Sigup_task([resp](RespSignup * response, 
            srpc::RPCContext * ctx) {
            if(ctx->success() && response->code() == 0) {
                resp->String("SUCCESS");
            }
        });
        //3.3 设置任务的属性: 序列化rpc请求
        rpcTask->serialize_input(&sigup_req);
        //3.4 交给序列去执行
        series->push_back(rpcTask);
    });

    server.POST("/file/upload", [](const HttpReq * req, 
        HttpResp * resp,
        SeriesWork * series){
        //解析请求
        auto queryList = req->query_list();
        string username = queryList["username"];
        string token = queryList["token"];
        //校验token
        //获取formdata
        if(req->content_type() == MULTIPART_FORM_DATA) {
            //获取文件的信息
            auto & formMap = req->form();
            string filename = formMap["file"].first;
            string content = formMap["file"].second;
            cout << "filename:" << filename << endl;
            cout << "content'size:" << content.size() << endl;

            //将文件内容写入本地文件
            string filepath = "./tmp/" + filename;
            mkdir("./tmp", 0755);
            
            int fd = open(filepath.c_str(), O_CREAT|O_RDWR, 0755);
            if(fd < 0) {
                perror("open");
                return;
            }
            write(fd, content.c_str(), content.size());
            close(fd);
            //获取hash值
            Hash hash(filepath);
            string filehash = hash.sha1();

            //备份准备，获取上传到OSS的路径信息
            /* OssUploader ossuploader; */
            string objectName = "oss/" + filename;
            //发送到消息队列
            Json toUploadFileInfo;
            toUploadFileInfo["filepath"] = filepath;
            toUploadFileInfo["objectName"] = objectName;
            using namespace AmqpClient;
            AmqpInfo amqpinfo;
            MessagePublisher publisher(amqpinfo, toUploadFileInfo.dump());
            publisher.doPublish();

            //更新数据库的操作
            string mysqlurl("mysql://root:1234@localhost");
            auto mysqlTask = WFTaskFactory::create_mysql_task(
                mysqlurl, 0,
                [resp](WFMySQLTask * mysqltask){
                    auto mysqlresp = mysqltask->get_resp();
                    int state = mysqltask->get_state();
                    int error = mysqltask->get_error();
                    if(state != WFT_STATE_SUCCESS) {
                        printf("error: %s\n", WFGlobal::get_error_string(state, error));
                        return;
                    }

                    //语法错误
                    if(mysqlresp->get_packet_type() == MYSQL_PACKET_HEADER_ERROR) {
                        printf("error_code: %d, msg: %s\n",
                            mysqlresp->get_error_code(),
                            mysqlresp->get_error_msg().c_str());
                        resp->set_status_code("500");
                        return;
                    }

                    using namespace protocol;
                    //通过迭代器访问结果
                    MySQLResultCursor cursor(mysqlresp);
                    if(cursor.get_affected_rows() == 1) {
                        //重定向
                        resp->set_status_code("302");
                        resp->headers["Location"] = "/file/upload/success";
                    } else {
                        resp->set_status_code("500");
                    }
                });
            string filesizestr = to_string(content.size());
            string sql = "INSERT INTO cloudisk.tbl_file(`file_sha1`, `file_name`,`file_size`, `file_addr`, `status`) VALUES (";
            sql += "'" + filehash + "', '" + filename + "', '" + filesizestr + "', '" + filepath + "',0);";
            sql += "INSERT INTO cloudisk.tbl_user_file(`user_name`, `file_sha1`, `file_size`, `file_name`,`status`)VALUES('";
            sql += username + "','" +  filehash + "','" + filesizestr + "', '" + filename + "', 0);";
            cout << "sql:\n" <<  sql << endl;
            mysqlTask->get_req()->set_query(sql);
            series->push_back(mysqlTask);
        }
    });

    server.POST("/file/downloadurl", [](const HttpReq * req, HttpResp * resp){
        auto queryList = req->query_list();
        string filename = queryList["filename"];
        cout << "filename:" << filename << endl;

        //从OSS中下载
        /* string getObjectName = "oss/" + filename; */
        /* OssUploader oss; */
        /* string url = oss.genreateDownloadUrl(getObjectName); */
        //从nginx下载服务器中下载
        string url = "http://192.168.30.128:8080/" + filename;
        cout << "download url:" << url << endl;
        resp->String(url);
    });


    server.GET("/file/download", [](const HttpReq * req, HttpResp * resp){
        auto querylist = req->query_list();
        string filename = querylist["filename"];
        string filehash = querylist["filehash"];
        string filesizestr = querylist["filesize"];
        int filesize = stoi(filesizestr);
        cout << "filesize:" << filesize << endl;

        //方案二: 下载文件 //用重定向
        resp->headers["Location"] = "http://192.168.30.128:8080/" + filename;
        resp->set_status_code("302");
        
        //方案一:
        /* string filepath = "./tmp/" + filename; */
        /* int fd = open(filepath.c_str(), O_RDONLY); */
        /* if(fd < 0) { */
        /*     perror("open"); */
        /*     return; */
        /* } */
        /* //读取文件 */
        /* char * buff  = new char[filesize+1](); */
        /* int ret = read(fd, buff, filesize); */
        /* close(fd); */
        /* //设置响应 */
        /* resp->append_output_body(buff, ret); */
        /* resp->headers["Content-Type"] = "application/octet-stream"; */
        /* resp->headers["Content-Disposition"] = "attachment;filename=" + filename; */
        /* delete [] buff; */
    });

    if(server.track().start(8888) == 0) {
        server.list_routes();
        waitGroup.wait();
        server.stop();
    } else {
        printf("server cannnot start\n");
    }
}


int main()
{
    test0();
    return 0;
}

